home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 10868 / 10868.xpi / modules / service.js < prev    next >
Text File  |  2010-02-02  |  52KB  |  1,560 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Bookmarks Sync.
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla.
  17.  * Portions created by the Initial Developer are Copyright (C) 2007
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *  Dan Mills <thunder@mozilla.com>
  22.  *  Myk Melez <myk@mozilla.org>
  23.  *  Anant Narayanan <anant@kix.in>
  24.  *
  25.  * Alternatively, the contents of this file may be used under the terms of
  26.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  27.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.  * in which case the provisions of the GPL or the LGPL are applicable instead
  29.  * of those above. If you wish to allow use of your version of this file only
  30.  * under the terms of either the GPL or the LGPL, and not to allow others to
  31.  * use your version of this file under the terms of the MPL, indicate your
  32.  * decision by deleting the provisions above and replace them with the notice
  33.  * and other provisions required by the GPL or the LGPL. If you do not delete
  34.  * the provisions above, a recipient may use your version of this file under
  35.  * the terms of any one of the MPL, the GPL or the LGPL.
  36.  *
  37.  * ***** END LICENSE BLOCK ***** */
  38.  
  39. const EXPORTED_SYMBOLS = ['Weave'];
  40.  
  41. const Cc = Components.classes;
  42. const Ci = Components.interfaces;
  43. const Cr = Components.results;
  44. const Cu = Components.utils;
  45.  
  46. // how long we should wait before actually syncing on idle
  47. const IDLE_TIME = 5; // xxxmpc: in seconds, should be preffable
  48.  
  49. // How long before refreshing the cluster
  50. const CLUSTER_BACKOFF = 5 * 60 * 1000; // 5 minutes
  51.  
  52. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  53. Cu.import("resource://weave/ext/Sync.js");
  54. Cu.import("resource://weave/log4moz.js");
  55. Cu.import("resource://weave/constants.js");
  56. Cu.import("resource://weave/util.js");
  57. Cu.import("resource://weave/auth.js");
  58. Cu.import("resource://weave/resource.js");
  59. Cu.import("resource://weave/base_records/wbo.js");
  60. Cu.import("resource://weave/base_records/crypto.js");
  61. Cu.import("resource://weave/base_records/keys.js");
  62. Cu.import("resource://weave/engines.js");
  63. Cu.import("resource://weave/identity.js");
  64. Cu.import("resource://weave/status.js");
  65. Cu.import("resource://weave/engines/clientData.js");
  66.  
  67. // for export
  68. let Weave = {};
  69. Cu.import("resource://weave/constants.js", Weave);
  70. Cu.import("resource://weave/util.js", Weave);
  71. Cu.import("resource://weave/auth.js", Weave);
  72. Cu.import("resource://weave/resource.js", Weave);
  73. Cu.import("resource://weave/base_records/keys.js", Weave);
  74. Cu.import("resource://weave/notifications.js", Weave);
  75. Cu.import("resource://weave/identity.js", Weave);
  76. Cu.import("resource://weave/status.js", Weave);
  77. Cu.import("resource://weave/stores.js", Weave);
  78. Cu.import("resource://weave/engines.js", Weave);
  79.  
  80. Cu.import("resource://weave/engines/bookmarks.js", Weave);
  81. Cu.import("resource://weave/engines/clientData.js", Weave);
  82. Cu.import("resource://weave/engines/forms.js", Weave);
  83. Cu.import("resource://weave/engines/history.js", Weave);
  84. Cu.import("resource://weave/engines/prefs.js", Weave);
  85. Cu.import("resource://weave/engines/passwords.js", Weave);
  86. Cu.import("resource://weave/engines/tabs.js", Weave);
  87.  
  88. Utils.lazy(Weave, 'Service', WeaveSvc);
  89.  
  90. /*
  91.  * Service singleton
  92.  * Main entry point into Weave's sync framework
  93.  */
  94.  
  95. function WeaveSvc() {
  96.   this._notify = Utils.notify("weave:service:");
  97. }
  98. WeaveSvc.prototype = {
  99.  
  100.   _lock: Utils.lock,
  101.   _catch: Utils.catch,
  102.   _isQuitting: false,
  103.   _loggedIn: false,
  104.   _syncInProgress: false,
  105.   _keyGenEnabled: true,
  106.  
  107.   // object for caching public and private keys
  108.   _keyPair: {},
  109.  
  110.   get username() {
  111.     return Svc.Prefs.get("username", "").toLowerCase();
  112.   },
  113.   set username(value) {
  114.     if (value) {
  115.       // Make sure all uses of this new username is lowercase
  116.       value = value.toLowerCase();
  117.       Svc.Prefs.set("username", value);
  118.     }
  119.     else
  120.       Svc.Prefs.reset("username");
  121.  
  122.     // fixme - need to loop over all Identity objects - needs some rethinking...
  123.     ID.get('WeaveID').username = value;
  124.     ID.get('WeaveCryptoID').username = value;
  125.  
  126.     // FIXME: need to also call this whenever the username pref changes
  127.     this._updateCachedURLs();
  128.   },
  129.  
  130.   get password password() ID.get("WeaveID").password,
  131.   set password password(value) ID.get("WeaveID").password = value,
  132.  
  133.   get passphrase passphrase() ID.get("WeaveCryptoID").password,
  134.   set passphrase passphrase(value) ID.get("WeaveCryptoID").password = value,
  135.  
  136.   get serverURL() Svc.Prefs.get("serverURL"),
  137.   set serverURL(value) {
  138.     // Only do work if it's actually changing
  139.     if (value == this.serverURL)
  140.       return;
  141.  
  142.     // A new server most likely uses a different cluster, so clear that
  143.     Svc.Prefs.set("serverURL", value);
  144.     Svc.Prefs.reset("clusterURL");
  145.   },
  146.  
  147.   get clusterURL() Svc.Prefs.get("clusterURL", ""),
  148.   set clusterURL(value) {
  149.     Svc.Prefs.set("clusterURL", value);
  150.     this._updateCachedURLs();
  151.   },
  152.  
  153.   get miscAPI() {
  154.     // Append to the serverURL if it's a relative fragment
  155.     let misc = Svc.Prefs.get("miscURL");
  156.     if (misc.indexOf(":") == -1)
  157.       misc = this.serverURL + misc;
  158.     return misc + "1/";
  159.   },
  160.  
  161.   get userAPI() {
  162.     // Append to the serverURL if it's a relative fragment
  163.     let user = Svc.Prefs.get("userURL");
  164.     if (user.indexOf(":") == -1)
  165.       user = this.serverURL + user;
  166.     return user + "1/";
  167.   },
  168.  
  169.   get isLoggedIn() { return this._loggedIn; },
  170.  
  171.   get isQuitting() { return this._isQuitting; },
  172.   set isQuitting(value) { this._isQuitting = value; },
  173.  
  174.   get keyGenEnabled() { return this._keyGenEnabled; },
  175.   set keyGenEnabled(value) { this._keyGenEnabled = value; },
  176.  
  177.   // nextSync is in milliseconds, but prefs can't hold that much
  178.   get nextSync() Svc.Prefs.get("nextSync", 0) * 1000,
  179.   set nextSync(value) Svc.Prefs.set("nextSync", Math.floor(value / 1000)),
  180.  
  181.   get syncInterval() {
  182.     // If we have a partial download, sync sooner if we're not mobile
  183.     if (Status.partial && Clients.clientType != "mobile")
  184.       return PARTIAL_DATA_SYNC;
  185.     return Svc.Prefs.get("syncInterval", MULTI_MOBILE_SYNC);
  186.   },
  187.   set syncInterval(value) Svc.Prefs.set("syncInterval", value),
  188.  
  189.   get syncThreshold() Svc.Prefs.get("syncThreshold", SINGLE_USER_THRESHOLD),
  190.   set syncThreshold(value) Svc.Prefs.set("syncThreshold", value),
  191.  
  192.   get globalScore() Svc.Prefs.get("globalScore", 0),
  193.   set globalScore(value) Svc.Prefs.set("globalScore", value),
  194.  
  195.   get numClients() Svc.Prefs.get("numClients", 0),
  196.   set numClients(value) Svc.Prefs.set("numClients", value),
  197.  
  198.   get locked() { return this._locked; },
  199.   lock: function Svc_lock() {
  200.     if (this._locked)
  201.       return false;
  202.     this._locked = true;
  203.     return true;
  204.   },
  205.   unlock: function Svc_unlock() {
  206.     this._locked = false;
  207.   },
  208.  
  209.   _updateCachedURLs: function _updateCachedURLs() {
  210.     // Nothing to cache yet if we don't have the building blocks
  211.     if (this.clusterURL == "" || this.username == "")
  212.       return;
  213.  
  214.     let storageAPI = this.clusterURL + Svc.Prefs.get("storageAPI") + "/";
  215.     let userBase = storageAPI + this.username + "/";
  216.     this._log.debug("Caching URLs under storage user base: " + userBase);
  217.  
  218.     // Generate and cache various URLs under the storage API for this user
  219.     this.infoURL = userBase + "info/collections";
  220.     this.storageURL = userBase + "storage/";
  221.     this.metaURL = this.storageURL + "meta/global";
  222.     PubKeys.defaultKeyUri = this.storageURL + "keys/pubkey";
  223.     PrivKeys.defaultKeyUri = this.storageURL + "keys/privkey";
  224.   },
  225.  
  226.   _checkCrypto: function WeaveSvc__checkCrypto() {
  227.     let ok = false;
  228.  
  229.     try {
  230.       let iv = Svc.Crypto.generateRandomIV();
  231.       if (iv.length == 24)
  232.         ok = true;
  233.  
  234.     } catch (e) {
  235.       this._log.debug("Crypto check failed: " + e);
  236.     }
  237.  
  238.     return ok;
  239.   },
  240.  
  241.   /**
  242.    * Prepare to initialize the rest of Weave after waiting a little bit
  243.    */
  244.   onStartup: function onStartup() {
  245.     Status.service = STATUS_DELAYED;
  246.  
  247.     // Figure out how many seconds to delay loading Weave based on the app
  248.     let wait = 0;
  249.     switch (Svc.AppInfo.ID) {
  250.       case FIREFOX_ID:
  251.         // Add one second delay for each tab in every window
  252.         let enum = Svc.WinMediator.getEnumerator("navigator:browser");
  253.         while (enum.hasMoreElements())
  254.           wait += enum.getNext().gBrowser.mTabs.length;
  255.         break;
  256.     }
  257.  
  258.     // Make sure we wait a little but but not too long in the worst case
  259.     wait = Math.ceil(Math.max(5, Math.min(20, wait)));
  260.  
  261.     this._initLogs();
  262.     this._log.info("Loading Weave " + WEAVE_VERSION + " in " + wait + " sec.");
  263.     Utils.delay(this._onStartup, wait * 1000, this, "_startupTimer");
  264.   },
  265.  
  266.   // one-time initialization like setting up observers and the like
  267.   // xxx we might need to split some of this out into something we can call
  268.   //     again when username/server/etc changes
  269.   _onStartup: function _onStartup() {
  270.     Status.service = STATUS_OK;
  271.     this.enabled = true;
  272.  
  273.     this._registerEngines();
  274.  
  275.     let ua = Cc["@mozilla.org/network/protocol;1?name=http"].
  276.       getService(Ci.nsIHttpProtocolHandler).userAgent;
  277.     this._log.info(ua);
  278.  
  279.     if (!this._checkCrypto()) {
  280.       this.enabled = false;
  281.       this._log.error("Could not load the Weave crypto component. Disabling " +
  282.                       "Weave, since it will not work correctly.");
  283.     }
  284.  
  285.     Svc.Observer.addObserver(this, "network:offline-status-changed", true);
  286.     Svc.Observer.addObserver(this, "private-browsing", true);
  287.     Svc.Observer.addObserver(this, "quit-application", true);
  288.     Svc.Observer.addObserver(this, "weave:service:sync:finish", true);
  289.     Svc.Observer.addObserver(this, "weave:service:sync:error", true);
  290.     Svc.Observer.addObserver(this, "weave:service:backoff:interval", true);
  291.     Svc.Observer.addObserver(this, "weave:engine:score:updated", true);
  292.  
  293.     if (!this.enabled)
  294.       this._log.info("Weave Sync disabled");
  295.  
  296.     // Create Weave identities (for logging in, and for encryption)
  297.     ID.set('WeaveID', new Identity('Mozilla Services Password', this.username));
  298.     Auth.defaultAuthenticator = new BasicAuthenticator(ID.get('WeaveID'));
  299.  
  300.     ID.set('WeaveCryptoID',
  301.            new Identity('Mozilla Services Encryption Passphrase', this.username));
  302.  
  303.     this._updateCachedURLs();
  304.  
  305.     // Send an event now that Weave service is ready
  306.     Svc.Obs.notify("weave:service:ready");
  307.  
  308.     if (Svc.Prefs.get("autoconnect"))
  309.       this._autoConnect();
  310.   },
  311.  
  312.   _initLogs: function WeaveSvc__initLogs() {
  313.     this._log = Log4Moz.repository.getLogger("Service.Main");
  314.     this._log.level =
  315.       Log4Moz.Level[Svc.Prefs.get("log.logger.service.main")];
  316.  
  317.     let formatter = new Log4Moz.BasicFormatter();
  318.     let root = Log4Moz.repository.rootLogger;
  319.     root.level = Log4Moz.Level[Svc.Prefs.get("log.rootLogger")];
  320.  
  321.     let capp = new Log4Moz.ConsoleAppender(formatter);
  322.     capp.level = Log4Moz.Level[Svc.Prefs.get("log.appender.console")];
  323.     root.addAppender(capp);
  324.  
  325.     let dapp = new Log4Moz.DumpAppender(formatter);
  326.     dapp.level = Log4Moz.Level[Svc.Prefs.get("log.appender.dump")];
  327.     root.addAppender(dapp);
  328.  
  329.     let verbose = Svc.Directory.get("ProfD", Ci.nsIFile);
  330.     verbose.QueryInterface(Ci.nsILocalFile);
  331.     verbose.append("weave");
  332.     verbose.append("logs");
  333.     verbose.append("verbose-log.txt");
  334.     if (!verbose.exists())
  335.       verbose.create(verbose.NORMAL_FILE_TYPE, PERMS_FILE);
  336.  
  337.     let maxSize = 65536; // 64 * 1024 (64KB)
  338.     this._debugApp = new Log4Moz.RotatingFileAppender(verbose, formatter, maxSize);
  339.     this._debugApp.level = Log4Moz.Level[Svc.Prefs.get("log.appender.debugLog")];
  340.     root.addAppender(this._debugApp);
  341.   },
  342.  
  343.   clearLogs: function WeaveSvc_clearLogs() {
  344.     this._debugApp.clear();
  345.   },
  346.  
  347.   /**
  348.    * Register the built-in engines for certain applications
  349.    */
  350.   _registerEngines: function WeaveSvc__registerEngines() {
  351.     let engines = [];
  352.     switch (Svc.AppInfo.ID) {
  353.       case FENNEC_ID:
  354.         engines = ["Bookmarks", "History", "Password", "Tab"];
  355.         break;
  356.  
  357.       case FIREFOX_ID:
  358.         engines = ["Bookmarks", "Form", "History", "Password", "Prefs", "Tab"];
  359.         break;
  360.  
  361.       case SEAMONKEY_ID:
  362.         engines = ["Form", "History", "Password", "Tab"];
  363.         break;
  364.     }
  365.  
  366.     // Grab the actual engine and register them
  367.     Engines.register(engines.map(function(name) Weave[name + "Engine"]));
  368.   },
  369.  
  370.   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
  371.                                          Ci.nsISupportsWeakReference]),
  372.  
  373.   // nsIObserver
  374.  
  375.   observe: function WeaveSvc__observe(subject, topic, data) {
  376.     switch (topic) {
  377.       case "network:offline-status-changed":
  378.         // Whether online or offline, we'll reschedule syncs
  379.         this._log.trace("Network offline status change: " + data);
  380.         this._checkSyncStatus();
  381.         break;
  382.       case "private-browsing":
  383.         // Entering or exiting private browsing? Reschedule syncs
  384.         this._log.trace("Private browsing change: " + data);
  385.         this._checkSyncStatus();
  386.         break;
  387.       case "quit-application":
  388.         this._onQuitApplication();
  389.         break;
  390.       case "weave:service:sync:error":
  391.         this._handleSyncError();
  392.         break;
  393.       case "weave:service:sync:finish":
  394.         this._scheduleNextSync();
  395.         this._syncErrors = 0;
  396.         break;
  397.       case "weave:service:backoff:interval":
  398.         let interval = data + Math.random() * data * 0.25; // required backoff + up to 25%
  399.         Status.backoffInterval = interval;
  400.         Status.minimumNextSync = Date.now() + data;
  401.         break;
  402.       case "weave:engine:score:updated":
  403.         this._handleScoreUpdate();
  404.         break;
  405.       case "idle":
  406.         this._log.trace("Idle time hit, trying to sync");
  407.         Svc.Idle.removeIdleObserver(this, this._idleTime);
  408.         this._idleTime = 0;
  409.         Utils.delay(function() this.sync(false), 0, this);
  410.         break;
  411.     }
  412.   },
  413.  
  414.   _handleScoreUpdate: function WeaveSvc__handleScoreUpdate() {
  415.     const SCORE_UPDATE_DELAY = 3000;
  416.     Utils.delay(this._calculateScore, SCORE_UPDATE_DELAY, this, "_scoreTimer");
  417.   },
  418.  
  419.   _calculateScore: function WeaveSvc_calculateScoreAndDoStuff() {
  420.     var engines = Engines.getEnabled();
  421.     for (let i = 0;i < engines.length;i++) {
  422.       this._log.trace(engines[i].name + ": score: " + engines[i].score);
  423.       this.globalScore += engines[i].score;
  424.       engines[i]._tracker.resetScore();
  425.     }
  426.  
  427.     this._log.trace("Global score updated: " + this.globalScore);
  428.     this._checkSyncStatus();
  429.   },
  430.  
  431.   // gets cluster from central LDAP server and returns it, or null on error
  432.   _findCluster: function _findCluster() {
  433.     this._log.debug("Finding cluster for user " + this.username);
  434.  
  435.     let fail;
  436.     let res = new Resource(this.userAPI + this.username + "/node/weave");
  437.     try {
  438.       let node = res.get();
  439.       switch (node.status) {
  440.         case 400:
  441.           Status.login = LOGIN_FAILED_LOGIN_REJECTED;
  442.           fail = "Find cluster denied: " + this._errorStr(node);
  443.           break;
  444.         case 404:
  445.           this._log.debug("Using serverURL as data cluster (multi-cluster support disabled)");
  446.           return this.serverURL;
  447.         case 0:
  448.         case 200:
  449.           if (node == "null")
  450.             node = null;
  451.           return node;
  452.         default:
  453.           this._log.debug("Unexpected response code: " + node.status);
  454.           break;
  455.       }
  456.     } catch (e) {
  457.       this._log.debug("Network error on findCluster");
  458.       Status.login = LOGIN_FAILED_NETWORK_ERROR;
  459.       fail = e;
  460.     }
  461.     throw fail;
  462.   },
  463.  
  464.   // gets cluster from central LDAP server and sets this.clusterURL
  465.   _setCluster: function _setCluster() {
  466.     // Make sure we didn't get some unexpected response for the cluster
  467.     let cluster = this._findCluster();
  468.     this._log.debug("cluster value = " + cluster);
  469.     if (cluster == null)
  470.       return false;
  471.  
  472.     // Don't update stuff if we already have the right cluster
  473.     if (cluster == this.clusterURL)
  474.       return false;
  475.  
  476.     this.clusterURL = cluster;
  477.     return true;
  478.   },
  479.  
  480.   // update cluster if required. returns false if the update was not required
  481.   _updateCluster: function _updateCluster() {
  482.     let cTime = Date.now();
  483.     let lastUp = parseFloat(Svc.Prefs.get("lastClusterUpdate"));
  484.     if (!lastUp || ((cTime - lastUp) >= CLUSTER_BACKOFF)) {
  485.       if (this._setCluster()) {
  486.         Svc.Prefs.set("lastClusterUpdate", cTime.toString());
  487.         return true;
  488.       }
  489.     }
  490.     return false;
  491.   },
  492.  
  493.   _verifyLogin: function _verifyLogin()
  494.     this._notify("verify-login", "", function() {
  495.       // Make sure we have a cluster to verify against
  496.       // this is a little weird, if we don't get a node we pretend
  497.       // to succeed, since that probably means we just don't have storage
  498.       if (this.clusterURL == "" && !this._setCluster()) {
  499.         Status.sync = NO_SYNC_NODE_FOUND;
  500.         Svc.Observer.notifyObservers(null, "weave:service:sync:delayed", "");
  501.         return true;
  502.       }
  503.  
  504.       try {
  505.         let test = new Resource(this.infoURL).get();
  506.         switch (test.status) {
  507.           case 200:
  508.             // The user is authenticated, so check the passphrase now
  509.             if (!this._verifyPassphrase()) {
  510.               Status.login = LOGIN_FAILED_INVALID_PASSPHRASE;
  511.               return false;
  512.             }
  513.  
  514.             // Username/password and passphrase all verified
  515.             Status.login = LOGIN_SUCCEEDED;
  516.             return true;
  517.  
  518.           case 401:
  519.           case 404:
  520.             // Check that we're verifying with the correct cluster
  521.             if (this._setCluster())
  522.               return this._verifyLogin();
  523.  
  524.             // We must have the right cluster, but the server doesn't expect us
  525.             Status.login = LOGIN_FAILED_LOGIN_REJECTED;
  526.             return false;
  527.  
  528.           default:
  529.             // Server didn't respond with something that we expected
  530.             this._checkServerError(test);
  531.             Status.login = LOGIN_FAILED_SERVER_ERROR;
  532.             return false;
  533.         }
  534.       }
  535.       catch (ex) {
  536.         // Must have failed on some network issue
  537.         this._log.debug("verifyLogin failed: " + Utils.exceptionStr(ex));
  538.         Status.login = LOGIN_FAILED_NETWORK_ERROR;
  539.         return false;
  540.       }
  541.     })(),
  542.  
  543.   _verifyPassphrase: function _verifyPassphrase()
  544.     this._catch(this._notify("verify-passphrase", "", function() {
  545.       // Don't allow empty/missing passphrase
  546.       if (!this.passphrase)
  547.         return false;
  548.  
  549.       try {
  550.         let pubkey = PubKeys.getDefaultKey();
  551.         let privkey = PrivKeys.get(pubkey.privateKeyUri);
  552.         return Svc.Crypto.verifyPassphrase(
  553.           privkey.payload.keyData, this.passphrase,
  554.           privkey.payload.salt, privkey.payload.iv
  555.         );
  556.       } catch (e) {
  557.         // this means no keys are present (or there's a network error)
  558.         return true;
  559.       }
  560.     }))(),
  561.  
  562.   changePassphrase: function WeaveSvc_changePassphrase(newphrase)
  563.     this._catch(this._notify("changepph", "", function() {
  564.       let pubkey = PubKeys.getDefaultKey();
  565.       let privkey = PrivKeys.get(pubkey.privateKeyUri);
  566.  
  567.       /* Re-encrypt with new passphrase.
  568.        * FIXME: verifyPassphrase first!
  569.        */
  570.       let newkey = Svc.Crypto.rewrapPrivateKey(privkey.payload.keyData,
  571.           this.passphrase, privkey.payload.salt,
  572.           privkey.payload.iv, newphrase);
  573.       privkey.payload.keyData = newkey;
  574.  
  575.       let resp = new Resource(privkey.uri).put(privkey);
  576.       if (!resp.success)
  577.         throw resp;
  578.  
  579.       // Save the new passphrase to the login manager for it to sync
  580.       this.passphrase = newphrase;
  581.       this.persistLogin();
  582.       return true;
  583.     }))(),
  584.  
  585.   changePassword: function WeaveSvc_changePassword(newpass)
  586.     this._notify("changepwd", "", function() {
  587.       let url = this.userAPI + this.username + "/password";
  588.       try {
  589.         let resp = new Resource(url).post(newpass);
  590.         if (resp.status != 200) {
  591.           this._log.debug("Password change failed: " + resp);
  592.           return false;
  593.         }
  594.       }
  595.       catch(ex) {
  596.         // Must have failed on some network issue
  597.         this._log.debug("changePassword failed: " + Utils.exceptionStr(ex));
  598.         return false;
  599.       }
  600.  
  601.       // Save the new password for requests and login manager
  602.       this.password = newpass;
  603.       this.persistLogin();
  604.       return true;
  605.     })(),
  606.  
  607.   resetPassphrase: function WeaveSvc_resetPassphrase(newphrase)
  608.     this._catch(this._notify("resetpph", "", function() {
  609.       /* Make remote commands ready so we have a list of clients beforehand */
  610.       this.prepCommand("logout", []);
  611.       let clientsBackup = Clients._store.clients;
  612.  
  613.       /* Wipe */
  614.       this.wipeServer();
  615.       PubKeys.clearCache();
  616.       PrivKeys.clearCache();
  617.  
  618.       /* Set remote commands before syncing */
  619.       Clients._store.clients = clientsBackup;
  620.       let username = this.username;
  621.       let password = this.password;
  622.       this.logout();
  623.  
  624.       /* Set this so UI is updated on next run */
  625.       this.passphrase = newphrase;
  626.  
  627.       /* Login in sync: this also generates new keys */
  628.       this.login(username, password, newphrase);
  629.       this.sync(true);
  630.       return true;
  631.     }))(),
  632.  
  633.   requestPasswordReset: function WeaveSvc_requestPasswordReset(username) {
  634.     let res = new Resource(Svc.Prefs.get("pwChangeURL"));
  635.     res.authenticator = new NoOpAuthenticator();
  636.     res.headers['Content-Type'] = 'application/x-www-form-urlencoded';
  637.     let ret = res.post('uid=' + username);
  638.     if (ret.indexOf("Further instructions have been sent") >= 0)
  639.       return true;
  640.     return false;
  641.   },
  642.  
  643.   _autoConnect: let (attempts = 0) function _autoConnect() {
  644.     let reason = "";
  645.     if (this._mpLocked())
  646.       reason = "master password still locked";
  647.  
  648.     // Can't autoconnect if we're missing these values
  649.     if (!reason) {
  650.       if (!this.username || !this.password || !this.passphrase)
  651.         return;
  652.  
  653.       // Nothing more to do on a successful login
  654.       if (this.login())
  655.         return;
  656.     }
  657.  
  658.     // Something failed, so try again some time later
  659.     let interval = this._calculateBackoff(++attempts, 60 * 1000);
  660.     this._log.debug("Autoconnect failed: " + (reason || Status.login) +
  661.       "; retry in " + Math.ceil(interval / 1000) + " sec.");
  662.     Utils.delay(function() this._autoConnect(), interval, this, "_autoTimer");
  663.   },
  664.  
  665.   _mpLocked: function _mpLocked() {
  666.     let modules = Cc["@mozilla.org/security/pkcs11moduledb;1"].
  667.                   getService(Ci.nsIPKCS11ModuleDB);
  668.     let sdrSlot = modules.findSlotByName("");
  669.     let status  = sdrSlot.status;
  670.     let slots = Ci.nsIPKCS11Slot;
  671.  
  672.     if (status == slots.SLOT_READY || status == slots.SLOT_LOGGED_IN)
  673.       return false;
  674.  
  675.     if (status == slots.SLOT_NOT_LOGGED_IN)
  676.       return true;
  677.  
  678.     this._log.debug("something wacky happened, pretending MP is locked");
  679.     return true;
  680.   },
  681.  
  682.   persistLogin: function persistLogin() {
  683.     // Canceled master password prompt can prevent these from succeeding
  684.     try {
  685.       ID.get("WeaveID").persist();
  686.       ID.get("WeaveCryptoID").persist();
  687.     }
  688.     catch(ex) {}
  689.   },
  690.  
  691.   login: function WeaveSvc_login(username, password, passphrase)
  692.     this._catch(this._lock(this._notify("login", "", function() {
  693.       this._loggedIn = false;
  694.       if (Svc.IO.offline)
  695.         throw "Application is offline, login should not be called";
  696.  
  697.       if (username)
  698.         this.username = username;
  699.       if (password)
  700.         this.password = password;
  701.       if (passphrase)
  702.         this.passphrase = passphrase;
  703.  
  704.       if (!this.username) {
  705.         Status.login = LOGIN_FAILED_NO_USERNAME;
  706.         throw "No username set, login failed";
  707.       }
  708.       if (!this.password) {
  709.         Status.login = LOGIN_FAILED_NO_PASSWORD;
  710.         throw "No password given or found in password manager";
  711.       }
  712.       this._log.info("Logging in user " + this.username);
  713.  
  714.       if (!this._verifyLogin()) {
  715.         // verifyLogin sets the failure states here
  716.         throw "Login failed: " + Status.login;
  717.       }
  718.  
  719.       // No need to try automatically connecting after a successful login
  720.       if (this._autoTimer)
  721.         this._autoTimer.clear();
  722.  
  723.       this._loggedIn = true;
  724.       // Try starting the sync timer now that we're logged in
  725.       this._checkSyncStatus();
  726.       Svc.Prefs.set("autoconnect", true);
  727.  
  728.       return true;
  729.     })))(),
  730.  
  731.   logout: function WeaveSvc_logout() {
  732.     // No need to do anything if we're already logged out
  733.     if (!this._loggedIn)
  734.       return;
  735.  
  736.     this._log.info("Logging out");
  737.     this._loggedIn = false;
  738.     this._keyPair = {};
  739.  
  740.     // Cancel the sync timer now that we're logged out
  741.     this._checkSyncStatus();
  742.     Svc.Prefs.set("autoconnect", false);
  743.  
  744.     Svc.Observer.notifyObservers(null, "weave:service:logout:finish", "");
  745.   },
  746.  
  747.   _errorStr: function WeaveSvc__errorStr(code) {
  748.     switch (code.toString()) {
  749.     case "1":
  750.       return "illegal-method";
  751.     case "2":
  752.       return "invalid-captcha";
  753.     case "3":
  754.       return "invalid-username";
  755.     case "4":
  756.       return "cannot-overwrite-resource";
  757.     case "5":
  758.       return "userid-mismatch";
  759.     case "6":
  760.       return "json-parse-failure";
  761.     case "7":
  762.       return "invalid-password";
  763.     case "8":
  764.       return "invalid-record";
  765.     case "9":
  766.       return "weak-password";
  767.     default:
  768.       return "generic-server-error";
  769.     }
  770.   },
  771.  
  772.   checkUsername: function WeaveSvc_checkUsername(username) {
  773.     let url = this.userAPI + username;
  774.     let res = new Resource(url);
  775.     res.authenticator = new NoOpAuthenticator();
  776.  
  777.     let data = "";
  778.     try {
  779.       data = res.get();
  780.       if (data.status == 200 && data == "0")
  781.         return "available";
  782.     }
  783.     catch(ex) {}
  784.  
  785.     // Convert to the error string, or default to generic on exception
  786.     return this._errorStr(data);
  787.   },
  788.  
  789.   createAccount: function WeaveSvc_createAccount(username, password, email,
  790.                                             captchaChallenge, captchaResponse)
  791.   {
  792.     let payload = JSON.stringify({
  793.       "password": password, "email": email,
  794.       "captcha-challenge": captchaChallenge,
  795.       "captcha-response": captchaResponse
  796.     });
  797.  
  798.     let url = this.userAPI + username;
  799.     let res = new Resource(url);
  800.     res.authenticator = new Weave.NoOpAuthenticator();
  801.  
  802.     let error = "generic-server-error";
  803.     try {
  804.       let register = res.put(payload);
  805.       if (register.success) {
  806.         this._log.info("Account created: " + register);
  807.         return null;
  808.       }
  809.  
  810.       // Must have failed, so figure out the reason
  811.       if (register.status == 400)
  812.         error = this._errorStr(register);
  813.     }
  814.     catch(ex) {
  815.       this._log.warn("Failed to create account: " + ex);
  816.     }
  817.  
  818.     return error;
  819.   },
  820.  
  821.   // stuff we need to to after login, before we can really do
  822.   // anything (e.g. key setup)
  823.   _remoteSetup: function WeaveSvc__remoteSetup() {
  824.     let reset = false;
  825.  
  826.     this._log.trace("Fetching global metadata record");
  827.     let meta = Records.import(this.metaURL);
  828.  
  829.     let remoteVersion = (meta && meta.payload.storageVersion)?
  830.       meta.payload.storageVersion : "";
  831.  
  832.     this._log.debug(["Weave Version:", WEAVE_VERSION, "Compatible:",
  833.       COMPATIBLE_VERSION, "Remote:", remoteVersion].join(" "));
  834.  
  835.     if (!meta || !meta.payload.storageVersion || !meta.payload.syncID ||
  836.         Svc.Version.compare(COMPATIBLE_VERSION, remoteVersion) > 0) {
  837.  
  838.       // abort the server wipe if the GET status was anything other than 404 or 200
  839.       let status = Records.response.status;
  840.       if (status != 200 && status != 404) {
  841.         this._checkServerError(Records.response);
  842.         Status.sync = METARECORD_DOWNLOAD_FAIL;
  843.         this._log.warn("Unknown error while downloading metadata record. " +
  844.                        "Aborting sync.");
  845.         return false;
  846.       }
  847.  
  848.       if (!meta)
  849.         this._log.info("No metadata record, server wipe needed");
  850.       if (meta && !meta.payload.syncID)
  851.         this._log.warn("No sync id, server wipe needed");
  852.       if (Svc.Version.compare(COMPATIBLE_VERSION, remoteVersion) > 0)
  853.         this._log.info("Server data is older than what Weave supports, server wipe needed");
  854.  
  855.       if (!this._keyGenEnabled) {
  856.         this._log.info("...and key generation is disabled.  Not wiping. " +
  857.                        "Aborting sync.");
  858.         Status.sync = DESKTOP_VERSION_OUT_OF_DATE;
  859.         return false;
  860.       }
  861.       reset = true;
  862.       this._log.info("Wiping server data");
  863.       this._freshStart();
  864.  
  865.       if (status == 404)
  866.         this._log.info("Metadata record not found, server wiped to ensure " +
  867.                        "consistency.");
  868.       else // 200
  869.         this._log.info("Server data wiped to ensure consistency after client " +
  870.                        "upgrade (" + remoteVersion + " -> " + WEAVE_VERSION + ")");
  871.  
  872.     } else if (Svc.Version.compare(remoteVersion, WEAVE_VERSION) > 0) {
  873.       Status.sync = VERSION_OUT_OF_DATE;
  874.       this._log.warn("Server data is of a newer Weave version, this client " +
  875.                      "needs to be upgraded.  Aborting sync.");
  876.       return false;
  877.  
  878.     } else if (meta.payload.syncID != Clients.syncID) {
  879.       this._log.warn("Meta.payload.syncID is " + meta.payload.syncID +
  880.                      ", Clients.syncID is " + Clients.syncID);
  881.       this.resetClient();
  882.       this._log.info("Reset client because of syncID mismatch.");
  883.       Clients.syncID = meta.payload.syncID;
  884.       this._log.info("Reset the client after a server/client sync ID mismatch");
  885.       this._updateRemoteVersion(meta);
  886.  
  887.       // XXX Bug 531005 Wait long enough to allow potentially another concurrent
  888.       // sync to finish generating the keypair and uploading them
  889.       Sync.sleep(15000);
  890.     }
  891.     // We didn't wipe the server and we're not out of date, so update remote
  892.     else
  893.       this._updateRemoteVersion(meta);
  894.  
  895.     let needKeys = true;
  896.     let pubkey = PubKeys.getDefaultKey();
  897.     if (!pubkey)
  898.       this._log.debug("Could not get public key");
  899.     else if (pubkey.keyData == null)
  900.       this._log.debug("Public key has no key data");
  901.     else {
  902.       // make sure we have a matching privkey
  903.       let privkey = PrivKeys.get(pubkey.privateKeyUri);
  904.       if (!privkey)
  905.         this._log.debug("Could not get private key");
  906.       else if (privkey.keyData == null)
  907.         this._log.debug("Private key has no key data");
  908.       else
  909.         return true;
  910.     }
  911.  
  912.     if (needKeys) {
  913.       if (PubKeys.response.status != 404 && PrivKeys.response.status != 404) {
  914.         this._log.warn("Couldn't download keys from server, aborting sync");
  915.         this._log.debug("PubKey HTTP status: " + PubKeys.response.status);
  916.         this._log.debug("PrivKey HTTP status: " + PrivKeys.response.status);
  917.         this._checkServerError(PubKeys.response);
  918.         this._checkServerError(PrivKeys.response);
  919.         Status.sync = KEYS_DOWNLOAD_FAIL;
  920.         return false;
  921.       }
  922.  
  923.       if (!this._keyGenEnabled) {
  924.         this._log.warn("Couldn't download keys from server, and key generation" +
  925.                        "is disabled.  Aborting sync");
  926.         Status.sync = NO_KEYS_NO_KEYGEN;
  927.         return false;
  928.       }
  929.  
  930.       if (!reset) {
  931.         this._log.warn("Calling freshStart from !reset case.");
  932.         this._freshStart();
  933.         this._log.info("Server data wiped to ensure consistency due to missing keys");
  934.       }
  935.  
  936.       let passphrase = ID.get("WeaveCryptoID");
  937.       if (passphrase.password) {
  938.         let keys = PubKeys.createKeypair(passphrase, PubKeys.defaultKeyUri,
  939.                                          PrivKeys.defaultKeyUri);
  940.         try {
  941.           // Upload and cache the keypair
  942.           PubKeys.uploadKeypair(keys);
  943.           PubKeys.set(keys.pubkey.uri, keys.pubkey);
  944.           PrivKeys.set(keys.privkey.uri, keys.privkey);
  945.           return true;
  946.         } catch (e) {
  947.           Status.sync = KEYS_UPLOAD_FAIL;
  948.           this._log.error("Could not upload keys: " + Utils.exceptionStr(e));
  949.         }
  950.       } else {
  951.         Status.sync = SETUP_FAILED_NO_PASSPHRASE;
  952.         this._log.warn("Could not get encryption passphrase");
  953.       }
  954.     }
  955.  
  956.     return false;
  957.   },
  958.  
  959.   /**
  960.    * Determine if a sync should run.
  961.    *
  962.    * @return Reason for not syncing; not-truthy if sync should run
  963.    */
  964.   _checkSync: function WeaveSvc__checkSync() {
  965.     let reason = "";
  966.     if (!this.enabled)
  967.       reason = kSyncWeaveDisabled;
  968.     else if (!this._loggedIn)
  969.       reason = kSyncNotLoggedIn;
  970.     else if (Svc.IO.offline)
  971.       reason = kSyncNetworkOffline;
  972.     else if (Svc.Private && Svc.Private.privateBrowsingEnabled)
  973.       // Svc.Private doesn't exist on Fennec -- don't assume it's there.
  974.       reason = kSyncInPrivateBrowsing;
  975.     else if (Status.minimumNextSync > Date.now())
  976.       reason = kSyncBackoffNotMet;
  977.  
  978.     return reason;
  979.   },
  980.  
  981.   /**
  982.    * Remove any timers/observers that might trigger a sync
  983.    */
  984.   _clearSyncTriggers: function _clearSyncTriggers() {
  985.     // Clear out any scheduled syncs
  986.     if (this._syncTimer)
  987.       this._syncTimer.clear();
  988.  
  989.     // Clear out a sync that's just waiting for idle if we happen to have one
  990.     try {
  991.       Svc.Idle.removeIdleObserver(this, this._idleTime);
  992.       this._idleTime = 0;
  993.     }
  994.     catch(ex) {}
  995.   },
  996.  
  997.   /**
  998.    * Check if we should be syncing and schedule the next sync, if it's not scheduled
  999.    */
  1000.   _checkSyncStatus: function WeaveSvc__checkSyncStatus() {
  1001.     // Should we be syncing now, if not, cancel any sync timers and return
  1002.     // if we're in backoff, we'll schedule the next sync
  1003.     let reason = this._checkSync();
  1004.     if (reason && reason != kSyncBackoffNotMet) {
  1005.       this._clearSyncTriggers();
  1006.       Status.service = STATUS_DISABLED;
  1007.       return;
  1008.     }
  1009.  
  1010.     // Only set the wait time to 0 if we need to sync right away
  1011.     let wait;
  1012.     if (this.globalScore > this.syncThreshold) {
  1013.       this._log.debug("Global Score threshold hit, triggering sync.");
  1014.       wait = 0;
  1015.     }
  1016.     this._scheduleNextSync(wait);
  1017.   },
  1018.  
  1019.   /**
  1020.    * Call sync() on an idle timer
  1021.    *
  1022.    * delay is optional
  1023.    */
  1024.   syncOnIdle: function WeaveSvc_syncOnIdle(delay) {
  1025.     // No need to add a duplicate idle observer
  1026.     if (this._idleTime)
  1027.       return;
  1028.  
  1029.     this._idleTime = delay || IDLE_TIME;
  1030.     this._log.debug("Idle timer created for sync, will sync after " +
  1031.                     this._idleTime + " seconds of inactivity.");
  1032.     Svc.Idle.addIdleObserver(this, this._idleTime);
  1033.   },
  1034.  
  1035.   /**
  1036.    * Set a timer for the next sync
  1037.    */
  1038.   _scheduleNextSync: function WeaveSvc__scheduleNextSync(interval) {
  1039.     // Figure out when to sync next if not given a interval to wait
  1040.     if (interval == null) {
  1041.       // Check if we had a pending sync from last time
  1042.       if (this.nextSync != 0)
  1043.         interval = this.nextSync - Date.now();
  1044.       // Use the bigger of default sync interval and backoff
  1045.       else
  1046.         interval = Math.max(this.syncInterval, Status.backoffInterval);
  1047.     }
  1048.  
  1049.     // Start the sync right away if we're already late
  1050.     if (interval <= 0) {
  1051.       this.syncOnIdle();
  1052.       return;
  1053.     }
  1054.  
  1055.     this._log.trace("Next sync in " + Math.ceil(interval / 1000) + " sec.");
  1056.     Utils.delay(function() this.syncOnIdle(), interval, this, "_syncTimer");
  1057.  
  1058.     // Save the next sync time in-case sync is disabled (logout/offline/etc.)
  1059.     this.nextSync = Date.now() + interval;
  1060.   },
  1061.  
  1062.   _syncErrors: 0,
  1063.   /**
  1064.    * Deal with sync errors appropriately
  1065.    */
  1066.   _handleSyncError: function WeaveSvc__handleSyncError() {
  1067.     this._syncErrors++;
  1068.  
  1069.     // do nothing on the first couple of failures, if we're not in backoff due to 5xx errors
  1070.     if (!Status.enforceBackoff) {
  1071.       if (this._syncErrors < 3) {
  1072.         this._scheduleNextSync();
  1073.         return;
  1074.       }
  1075.       Status.enforceBackoff = true;
  1076.     }
  1077.  
  1078.     const MINIMUM_BACKOFF_INTERVAL = 15 * 60 * 1000;     // 15 minutes
  1079.     let interval = this._calculateBackoff(this._syncErrors, MINIMUM_BACKOFF_INTERVAL);
  1080.  
  1081.     this._scheduleNextSync(interval);
  1082.  
  1083.     let d = new Date(Date.now() + interval);
  1084.     this._log.config("Starting backoff, next sync at:" + d.toString());
  1085.   },
  1086.  
  1087.   /**
  1088.    * Sync up engines with the server.
  1089.    */
  1090.   sync: function sync()
  1091.     this._catch(this._lock(this._notify("sync", "", function() {
  1092.       Status.resetSync();
  1093.     if (Svc.Prefs.isSet("firstSync")) {
  1094.       switch(Svc.Prefs.get("firstSync")) {
  1095.         case "wipeClient":
  1096.           this.wipeClient();
  1097.           break;
  1098.         case "wipeRemote":
  1099.           this.wipeRemote(Engines.getAll().map(function(e) e.name));
  1100.           break;
  1101.         default:
  1102.           this._scheduleNextSync();
  1103.           return;
  1104.       }
  1105.     }
  1106.  
  1107.     // if we don't have a node, get one.  if that fails, retry in 10 minutes
  1108.     if (this.clusterURL == "" && !this._setCluster()) {
  1109.       this._scheduleNextSync(10 * 60 * 1000);
  1110.       return;
  1111.     }
  1112.  
  1113.     // Make sure we should sync or record why we shouldn't
  1114.     let reason = this._checkSync();
  1115.     if (reason) {
  1116.       // this is a purposeful abort rather than a failure, so don't set
  1117.       // any status bits
  1118.       reason = "Can't sync: " + reason;
  1119.       throw reason;
  1120.     }
  1121.  
  1122.     // Clear out any potentially pending syncs now that we're syncing
  1123.     this._clearSyncTriggers();
  1124.     this.nextSync = 0;
  1125.  
  1126.     this.globalScore = 0;
  1127.  
  1128.     if (!(this._remoteSetup()))
  1129.       throw "aborting sync, remote setup failed";
  1130.  
  1131.     // Ping the server with a special info request once a day
  1132.     let infoURL = this.infoURL;
  1133.     let now = Math.floor(Date.now() / 1000);
  1134.     let lastPing = Svc.Prefs.get("lastPing", 0);
  1135.     if (now - lastPing > 86400) { // 60 * 60 * 24
  1136.       infoURL += "?v=" + WEAVE_VERSION;
  1137.       Svc.Prefs.set("lastPing", now);
  1138.     }
  1139.  
  1140.     // Figure out what the last modified time is for each collection
  1141.     let info = new Resource(infoURL).get();
  1142.     if (!info.success)
  1143.       throw "aborting sync, failed to get collections";
  1144.  
  1145.     // Convert the response to an object and read out the modified times
  1146.     for each (let engine in [Clients].concat(Engines.getAll()))
  1147.       engine.lastModified = info.obj[engine.name] || 0;
  1148.  
  1149.     this._log.trace("Refreshing client list");
  1150.     Clients.sync();
  1151.  
  1152.     // Process the incoming commands if we have any
  1153.     if (Clients.getClients()[Clients.clientID].commands) {
  1154.       try {
  1155.         if (!(this.processCommands())) {
  1156.           Status.sync = ABORT_SYNC_COMMAND;
  1157.           throw "aborting sync, process commands said so";
  1158.         }
  1159.  
  1160.         // Repeat remoteSetup in-case the commands forced us to reset
  1161.         if (!(this._remoteSetup()))
  1162.           throw "aborting sync, remote setup failed after processing commands";
  1163.       }
  1164.       finally {
  1165.         // Always immediately push back the local client (now without commands)
  1166.         Clients.sync();
  1167.       }
  1168.     }
  1169.  
  1170.     // Update the client mode now because it might change what we sync
  1171.     this._updateClientMode();
  1172.  
  1173.     try {
  1174.       for each (let engine in Engines.getEnabled()) {
  1175.         // If there's any problems with syncing the engine, report the failure
  1176.         if (!(this._syncEngine(engine)) || Status.enforceBackoff) {
  1177.           this._log.info("Aborting sync");
  1178.           break;
  1179.         }
  1180.       }
  1181.  
  1182.       if (this._syncError)
  1183.         throw "Some engines did not sync correctly";
  1184.       else {
  1185.         Svc.Prefs.set("lastSync", new Date().toString());
  1186.         Status.sync = SYNC_SUCCEEDED;
  1187.         this._log.info("Sync completed successfully");
  1188.       }
  1189.     } finally {
  1190.       this._syncError = false;
  1191.       Svc.Prefs.reset("firstSync");
  1192.     }
  1193.   })))(),
  1194.  
  1195.   /**
  1196.    * Process the locally stored clients list to figure out what mode to be in
  1197.    */
  1198.   _updateClientMode: function _updateClientMode() {
  1199.     let numClients = 0;
  1200.     let hasMobile = false;
  1201.  
  1202.     // Check how many and what type of clients we have
  1203.     for each (let {type} in Clients.getClients()) {
  1204.       numClients++;
  1205.       hasMobile = hasMobile || type == "mobile";
  1206.     }
  1207.  
  1208.     // Nothing to do if it's the same amount
  1209.     if (this.numClients == numClients)
  1210.       return;
  1211.  
  1212.     this._log.debug("Client count: " + this.numClients + " -> " + numClients);
  1213.     this.numClients = numClients;
  1214.  
  1215.     let tabEngine = Engines.get("tabs");
  1216.     if (numClients == 1) {
  1217.       this.syncInterval = SINGLE_USER_SYNC;
  1218.       this.syncThreshold = SINGLE_USER_THRESHOLD;
  1219.     }
  1220.     else {
  1221.       this.syncInterval = hasMobile ? MULTI_MOBILE_SYNC : MULTI_DESKTOP_SYNC;
  1222.       this.syncThreshold = hasMobile ? MULTI_MOBILE_THRESHOLD : MULTI_DESKTOP_THRESHOLD;
  1223.     }
  1224.   },
  1225.  
  1226.   // returns true if sync should proceed
  1227.   // false / no return value means sync should be aborted
  1228.   _syncEngine: function WeaveSvc__syncEngine(engine) {
  1229.     try {
  1230.       engine.sync();
  1231.       return true;
  1232.     }
  1233.     catch(e) {
  1234.       // maybe a 401, cluster update needed?
  1235.       if (e.status == 401 && this._updateCluster())
  1236.         return this._syncEngine(engine);
  1237.  
  1238.       this._checkServerError(e);
  1239.  
  1240.       Status.engines = [engine.name, e.failureCode || ENGINE_UNKNOWN_FAIL];
  1241.  
  1242.       this._syncError = true;
  1243.       this._log.debug(Utils.exceptionStr(e));
  1244.       return true;
  1245.     }
  1246.     finally {
  1247.       // If this engine has more to fetch, remember that globally
  1248.       if (engine.toFetch != null && engine.toFetch.length > 0)
  1249.         Status.partial = true;
  1250.     }
  1251.   },
  1252.  
  1253.   _freshStart: function WeaveSvc__freshStart() {
  1254.     this.resetClient();
  1255.  
  1256.     this._log.debug("Uploading new metadata record from freshStart");
  1257.     let meta = new WBORecord(this.metaURL);
  1258.     meta.payload.syncID = Clients.syncID;
  1259.     this._updateRemoteVersion(meta);
  1260.  
  1261.     // Wipe everything we know about except meta because we just uploaded it
  1262.     let collections = [Clients].concat(Engines.getAll()).map(function(engine) {
  1263.       return engine.name;
  1264.     });
  1265.     this.wipeServer(["crypto", "keys"].concat(collections));
  1266.   },
  1267.  
  1268.   _updateRemoteVersion: function WeaveSvc__updateRemoteVersion(meta) {
  1269.     // Don't update if the remote version is already newer
  1270.     if (Svc.Version.compare(meta.payload.storageVersion, WEAVE_VERSION) >= 0)
  1271.       return;
  1272.  
  1273.     this._log.debug("Setting meta payload storage version to " + WEAVE_VERSION);
  1274.     meta.payload.storageVersion = WEAVE_VERSION;
  1275.     let resp = new Resource(meta.uri).put(meta);
  1276.     if (!resp.success)
  1277.       throw resp;
  1278.   },
  1279.  
  1280.  
  1281.   /**
  1282.    * Check to see if this is a failure
  1283.    *
  1284.    */
  1285.   _checkServerError: function WeaveSvc__checkServerError(resp) {
  1286.     if (Utils.checkStatus(resp.status, null, [500, [502, 504]])) {
  1287.       Status.enforceBackoff = true;
  1288.       if (resp.status == 503 && resp.headers["Retry-After"])
  1289.         Observers.notify("weave:service:backoff:interval", parseInt(resp.headers["Retry-After"], 10));
  1290.     }
  1291.   },
  1292.   /**
  1293.    * Return a value for a backoff interval.  Maximum is eight hours, unless
  1294.    * Status.backoffInterval is higher.
  1295.    *
  1296.    */
  1297.   _calculateBackoff: function WeaveSvc__calculateBackoff(attempts, base_interval) {
  1298.     const MAXIMUM_BACKOFF_INTERVAL = 8 * 60 * 60 * 1000; // 8 hours
  1299.     let backoffInterval = attempts *
  1300.                           (Math.floor(Math.random() * base_interval) +
  1301.                            base_interval);
  1302.     return Math.max(Math.min(backoffInterval, MAXIMUM_BACKOFF_INTERVAL), Status.backoffInterval);
  1303.   },
  1304.  
  1305.   /**
  1306.    * Wipe all user data from the server.
  1307.    *
  1308.    * @param engines [optional]
  1309.    *        Array of engine names to wipe. If not given, all engines are used.
  1310.    */
  1311.   wipeServer: function WeaveSvc_wipeServer(engines)
  1312.     this._catch(this._notify("wipe-server", "", function() {
  1313.       // Grab all the collections for the user and delete each one
  1314.       let info = new Resource(this.infoURL).get();
  1315.       for (let name in info.obj) {
  1316.         try {
  1317.           // If we have a list of engines, make sure it's one we want
  1318.           if (engines && engines.indexOf(name) == -1)
  1319.             continue;
  1320.  
  1321.           new Resource(this.storageURL + name).delete();
  1322.         }
  1323.         catch(ex) {
  1324.           this._log.debug("Exception on wipe of '" + name + "': " + Utils.exceptionStr(ex));
  1325.         }
  1326.       }
  1327.     }))(),
  1328.  
  1329.   /**
  1330.    * Wipe all local user data.
  1331.    *
  1332.    * @param engines [optional]
  1333.    *        Array of engine names to wipe. If not given, all engines are used.
  1334.    */
  1335.   wipeClient: function WeaveSvc_wipeClient(engines)
  1336.     this._catch(this._notify("wipe-client", "", function() {
  1337.       // If we don't have any engines, reset the service and wipe all engines
  1338.       if (!engines) {
  1339.         // Clear out any service data
  1340.         this.resetService();
  1341.  
  1342.         engines = [Clients].concat(Engines.getAll());
  1343.       }
  1344.       // Convert the array of names into engines
  1345.       else
  1346.         engines = Engines.get(engines);
  1347.  
  1348.       // Fully wipe each engine if it's able to decrypt data
  1349.       for each (let engine in engines)
  1350.         if (engine._testDecrypt())
  1351.           engine.wipeClient();
  1352.  
  1353.       // Save the password/passphrase just in-case they aren't restored by sync
  1354.       this.persistLogin();
  1355.     }))(),
  1356.  
  1357.   /**
  1358.    * Wipe all remote user data by wiping the server then telling each remote
  1359.    * client to wipe itself.
  1360.    *
  1361.    * @param engines [optional]
  1362.    *        Array of engine names to wipe. If not given, all engines are used.
  1363.    */
  1364.   wipeRemote: function WeaveSvc_wipeRemote(engines)
  1365.     this._catch(this._notify("wipe-remote", "", function() {
  1366.       // Make sure stuff gets uploaded
  1367.       Clients.resetSyncID();
  1368.  
  1369.       // Clear out any server data
  1370.       this.wipeServer(engines);
  1371.  
  1372.       // Only wipe the engines provided
  1373.       if (engines)
  1374.         engines.forEach(function(e) this.prepCommand("wipeEngine", [e]), this);
  1375.       // Tell the remote machines to wipe themselves
  1376.       else
  1377.         this.prepCommand("wipeAll", []);
  1378.  
  1379.       // Make sure the changed clients get updated
  1380.       Clients.sync();
  1381.     }))(),
  1382.  
  1383.   /**
  1384.    * Reset local service information like logs, sync times, caches.
  1385.    */
  1386.   resetService: function WeaveSvc_resetService()
  1387.     this._catch(this._notify("reset-service", "", function() {
  1388.       // First drop old logs to track client resetting behavior
  1389.       this.clearLogs();
  1390.       this._log.info("Logs reinitialized for service reset");
  1391.  
  1392.       // Pretend we've never synced to the server and drop cached data
  1393.       Clients.resetSyncID();
  1394.       Svc.Prefs.reset("lastSync");
  1395.       for each (let cache in [PubKeys, PrivKeys, CryptoMetas, Records])
  1396.         cache.clearCache();
  1397.     }))(),
  1398.  
  1399.   /**
  1400.    * Reset the client by getting rid of any local server data and client data.
  1401.    *
  1402.    * @param engines [optional]
  1403.    *        Array of engine names to reset. If not given, all engines are used.
  1404.    */
  1405.   resetClient: function WeaveSvc_resetClient(engines)
  1406.     this._catch(this._notify("reset-client", "", function() {
  1407.       // If we don't have any engines, reset everything including the service
  1408.       if (!engines) {
  1409.         // Clear out any service data
  1410.         this.resetService();
  1411.  
  1412.         engines = [Clients].concat(Engines.getAll());
  1413.       }
  1414.       // Convert the array of names into engines
  1415.       else
  1416.         engines = Engines.get(engines);
  1417.  
  1418.       // Have each engine drop any temporary meta data
  1419.       for each (let engine in engines)
  1420.         engine.resetClient();
  1421.  
  1422.       // XXX Bug 480448: Delete any snapshots from old code
  1423.       try {
  1424.         let cruft = Svc.Directory.get("ProfD", Ci.nsIFile);
  1425.         cruft.QueryInterface(Ci.nsILocalFile);
  1426.         cruft.append("weave");
  1427.         cruft.append("snapshots");
  1428.         if (cruft.exists())
  1429.           cruft.remove(true);
  1430.       } catch (e) {
  1431.         this._log.debug("Could not remove old snapshots: " + Utils.exceptionStr(e));
  1432.       }
  1433.     }))(),
  1434.  
  1435.   /**
  1436.    * A hash of valid commands that the client knows about. The key is a command
  1437.    * and the value is a hash containing information about the command such as
  1438.    * number of arguments and description.
  1439.    */
  1440.   _commands: [
  1441.     ["resetAll", 0, "Clear temporary local data for all engines"],
  1442.     ["resetEngine", 1, "Clear temporary local data for engine"],
  1443.     ["wipeAll", 0, "Delete all client data for all engines"],
  1444.     ["wipeEngine", 1, "Delete all client data for engine"],
  1445.     ["logout", 0, "Log out client"],
  1446.   ].reduce(function WeaveSvc__commands(commands, entry) {
  1447.     commands[entry[0]] = {};
  1448.     for (let [i, attr] in Iterator(["args", "desc"]))
  1449.       commands[entry[0]][attr] = entry[i + 1];
  1450.     return commands;
  1451.   }, {}),
  1452.  
  1453.   /**
  1454.    * Check if the local client has any remote commands and perform them.
  1455.    *
  1456.    * @return False to abort sync
  1457.    */
  1458.   processCommands: function WeaveSvc_processCommands()
  1459.     this._notify("process-commands", "", function() {
  1460.       let info = Clients.getInfo(Clients.clientID);
  1461.       let commands = info.commands;
  1462.  
  1463.       // Immediately clear out the commands as we've got them locally
  1464.       delete info.commands;
  1465.       Clients.setInfo(Clients.clientID, info);
  1466.  
  1467.       // Process each command in order
  1468.       for each ({command: command, args: args} in commands) {
  1469.         this._log.debug("Processing command: " + command + "(" + args + ")");
  1470.  
  1471.         let engines = [args[0]];
  1472.         switch (command) {
  1473.           case "resetAll":
  1474.             engines = null;
  1475.             // Fallthrough
  1476.           case "resetEngine":
  1477.             this.resetClient(engines);
  1478.             break;
  1479.           case "wipeAll":
  1480.             engines = null;
  1481.             // Fallthrough
  1482.           case "wipeEngine":
  1483.             this.wipeClient(engines);
  1484.             break;
  1485.           case "logout":
  1486.             this.logout();
  1487.             return false;
  1488.           default:
  1489.             this._log.debug("Received an unknown command: " + command);
  1490.             break;
  1491.         }
  1492.       }
  1493.  
  1494.       return true;
  1495.     })(),
  1496.  
  1497.   /**
  1498.    * Prepare to send a command to each remote client. Calling this doesn't
  1499.    * actually sync the command data to the server. If the client already has
  1500.    * the command/args pair, it won't get a duplicate action.
  1501.    *
  1502.    * @param command
  1503.    *        Command to invoke on remote clients
  1504.    * @param args
  1505.    *        Array of arguments to give to the command
  1506.    */
  1507.   prepCommand: function WeaveSvc_prepCommand(command, args) {
  1508.     let commandData = this._commands[command];
  1509.     // Don't send commands that we don't know about
  1510.     if (commandData == null) {
  1511.       this._log.error("Unknown command to send: " + command);
  1512.       return;
  1513.     }
  1514.     // Don't send a command with the wrong number of arguments
  1515.     else if (args == null || args.length != commandData.args) {
  1516.       this._log.error("Expected " + commandData.args + " args for '" +
  1517.                       command + "', but got " + args);
  1518.       return;
  1519.     }
  1520.  
  1521.     // Package the command/args pair into an object
  1522.     let action = {
  1523.       command: command,
  1524.       args: args,
  1525.     };
  1526.     let actionStr = command + "(" + args + ")";
  1527.  
  1528.     // Convert args into a string to simplify array comparisons
  1529.     let jsonArgs = JSON.stringify(args);
  1530.     let notDupe = function(action) action.command != command ||
  1531.       JSON.stringify(action.args) != jsonArgs;
  1532.  
  1533.     this._log.info("Sending clients: " + actionStr + "; " + commandData.desc);
  1534.  
  1535.     // Add the action to each remote client
  1536.     for (let guid in Clients.getClients()) {
  1537.       // Don't send commands to the local client
  1538.       if (guid == Clients.clientID)
  1539.         continue;
  1540.  
  1541.       let info = Clients.getInfo(guid);
  1542.       // Set the action to be a new commands array if none exists
  1543.       if (info.commands == null)
  1544.         info.commands = [action];
  1545.       // Add the new action if there are no duplicates
  1546.       else if (info.commands.every(notDupe))
  1547.         info.commands.push(action);
  1548.       // Must have been a dupe.. skip!
  1549.       else
  1550.         continue;
  1551.  
  1552.       Clients.setInfo(guid, info);
  1553.       this._log.trace("Client " + guid + " got a new action: " + actionStr);
  1554.     }
  1555.   },
  1556. };
  1557.  
  1558. // Load Weave on the first time this file is loaded
  1559. Weave.Service.onStartup();
  1560.